// Copyright 2010-2012, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_CAST_H_
#define LIBV_CAST_H_

#include <limits>
#include <stdexcept>
#include <string>
#include <sstream>

#include <libv/check.h>
#include <libv/static_assert.h>
#include <libv/stdint.h>

#undef min
#undef max



namespace libv
{

/** Корректное с точки зрения стандарта преобразование типа указателей.
  * @note Стандарт не гарантирует то, что указатели на разные типы
  * данных имеют одинаковый размер и структуру. reinterpret_cast выполняет
  * приведение «в лоб», при этом нет никакой гарантии того, что при этом не
  * произойдет потери данных и полученный указатель будет валиден.
  * Двойной static_cast - безопасная замена reinterpret_cast.
  * Стандарт гарантирует, что указатель на void имеет достаточный размер,
  * чтобы уместить указатель на любые данные. */
template<class TTo, class TFrom>
inline TTo* PointerCast(TFrom* from_ptr) /* throw() */
{
    return static_cast<TTo*>(static_cast<void*>(from_ptr));
}


template<class TTo, class TFrom>
inline const TTo* PointerCast(const TFrom* from_ptr) /* throw() */
{
    return static_cast<const TTo*>(static_cast<const void*>(from_ptr));
}


/** Корректное с точки зрения стандарта преобразование типа указателей,
  * снимающее константность.
  * Запись по полученному адресу приведет к undefined behavior.
  * Использовать только для совместимости со старым кодом. */
template<class TTo, class TFrom>
inline TTo* PointerConstCast(const TFrom* from_ptr) /* throw() */
{
    return static_cast<TTo*>(static_cast<void*>(const_cast<TFrom*>(from_ptr)));
}





/** Ошибка преобразования целых чисел. */
class IntegerCastException : public CheckException
{
public:
    explicit IntegerCastException(const ::std::string& msg) throw()
        : CheckException(::std::string("IntegerCast : ") + msg) {}

    virtual ~IntegerCastException() throw() {}
};


namespace internal
{

#define LIBV_DEFINE_TYPE2STR(T) \
    inline const char* Type2Str(const T&) { return #T; }
LIBV_DEFINE_TYPE2STR(char)
LIBV_DEFINE_TYPE2STR(int8_t)
LIBV_DEFINE_TYPE2STR(int16_t)
LIBV_DEFINE_TYPE2STR(int32_t)
LIBV_DEFINE_TYPE2STR(int64_t)
LIBV_DEFINE_TYPE2STR(uint8_t)
LIBV_DEFINE_TYPE2STR(uint16_t)
LIBV_DEFINE_TYPE2STR(uint32_t)
LIBV_DEFINE_TYPE2STR(uint64_t)
#undef LIBV_DEFINE_TYPE2STR

template<class T> const char* Type2Str(const T&) {return "unknown type";}

template<class TTo, class TFrom>
::std::string OutOfRangeErrorMsg(const TTo&, const TFrom& from)
{
    return ::std::string("from == ") + Int2Str(from) +
            " that is out of range of type To (" + Type2Str(TTo()) + "): [" +
            Int2Str(::std::numeric_limits<TTo>::min()) + "," +
            Int2Str(::std::numeric_limits<TTo>::max()) + "]";
}

template<class TTo, class TFrom>
::std::string NegativeToUnsignedErrorMsg(const TTo&, const TFrom& from)
{
    return ::std::string("from == ") + Int2Str(from) +
           ", but type To is unsigned (" + Type2Str(TTo()) + ")";
}


template<class Tint>
::std::string Int2Str(const Tint x)
{
    ::std::stringstream s;
    s << x;
    return s.str();
}

inline ::std::string Int2Str(const char x)  // char должно выводиться как число.
{
    ::std::stringstream s;
    s << static_cast<int>(x);
    return s.str();
}




// u->s, u->u
template<class TTo, class TFrom, bool Tto_is_signed, bool Tfrom_is_signed>
struct IntegerCastImpl
{
    static TTo Do(const TFrom from)
    {
        if ( from > static_cast<uint64_t>(::std::numeric_limits<TTo>::max()) )
            throw IntegerCastException(OutOfRangeErrorMsg(TTo(), from));

        return static_cast<TTo>(from);
    }
};

// s->s
template<class TTo, class TFrom>
struct IntegerCastImpl<TTo, TFrom, true, true>
{
    static TTo Do(const TFrom from)
    {
        if ( from < ::std::numeric_limits<TTo>::min() ||
             from > ::std::numeric_limits<TTo>::max() )
            throw IntegerCastException(OutOfRangeErrorMsg(TTo(), from));

        return static_cast<TTo>(from);
    }
};

// s->u
template<class TTo, class TFrom>
struct IntegerCastImpl<TTo, TFrom, false, true>
{
    static TTo Do(const TFrom from)
    {
        if ( from < 0 )
            throw IntegerCastException(NegativeToUnsignedErrorMsg(TTo(), from));

        if ( static_cast<uint64_t>(from) > ::std::numeric_limits<TTo>::max() )
            throw IntegerCastException(OutOfRangeErrorMsg(TTo(), from));

        return static_cast<TTo>(from);
    }
};

}  // internal


/** Преобразование между целочисленными типами с проверками на границы.
  * @exception IntegerCastException */
template<class TTo, class TFrom>
inline TTo IntegerCast(const TFrom from)
{
    LIBV_STATIC_ASSERT(::std::numeric_limits<TFrom>::is_integer &&
                       ::std::numeric_limits<TTo>::is_integer);

    const bool from_is_signed = ::std::numeric_limits<TFrom>::is_signed;
    const bool to_is_signed = ::std::numeric_limits<TTo>::is_signed;

    return internal::IntegerCastImpl<TTo, TFrom, to_is_signed, from_is_signed>::
           Do(from);
}

}  // libv

#endif  // LIBV_CAST_H_
